package com.legsem.jhosync.model;

import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.Random;
import java.util.UUID;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.dbutils.ResultSetHandler;

import com.legsem.jhosync.Constants;

/**
 * A User must either pre-exist in the data store (usually an administrator) or
 * will dynamically be created if the application agrees.
 * <p/>
 * A User must not be confused with a Client which represent devices connected
 * to the server.
 * <p/>
 * Clients may also be required to authenticate but this is not handled here
 * 
 */
public class User extends Model implements ResultSetHandler < User > {

    private static final long serialVersionUID = 1L;

    public static final String TABLE_NAME = "USERS";

    public static final String SQL_CREATE = "CREATE TABLE " + TABLE_NAME
            + " (\n" + "         LOGIN             VARCHAR (255),\n"
            + "         EMAIL             VARCHAR (255),\n"
            + "         SALT              CHAR (10),\n"
            + "         HASHED_PASSWORD   VARCHAR (255),\n"
            + "         ADMIN             INTEGER,\n"
            + "         TOKEN             CHAR (32),\n"
            + "         PRIMARY KEY ( LOGIN ));\n";

    public static final String SQL_DROP = "DROP TABLE " + TABLE_NAME + ";\n";

    private String login;
    private String email;
    private String salt;
    private String hashedPassword;
    private int admin;
    private String token;

    private static final String CHARS = "abcdefghijklmnopqrstuvwxyz"
            + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789";

    public User() {

    }

    public User(String login) {
        this.login = login;
    }

    public User(String login, String email, String password) {
        this.login = login;
        this.email = email;
        setPassword(password);
    }

    public static String getSqlCreate() {
        return SQL_CREATE;
    }

    public static String getSqlDrop() {
        return SQL_DROP;
    }

    public void createToken() {
        token = UUID.randomUUID().toString().replace("-", "");
    }

    public static User find(String login) throws SQLException {
        return find(login, false);
    }

    public static User findForUpdate(String login) throws SQLException {
        return find(login, true);
    }

    public static User find(String login, boolean forUpdate)
            throws SQLException {
        return run.query(getConnection(), "SELECT * FROM " + TABLE_NAME
                + " WHERE LOGIN=?" + ((forUpdate) ? " FOR UPDATE" : ""),
                new User(), login);
    }

    public int create() throws SQLException {
        return run.update(getConnection(), "INSERT INTO " + TABLE_NAME
                + " VALUES (?, ?, ? , ?, ?, ?)", login, email, salt,
                hashedPassword, admin, token);
    }

    public int delete() throws SQLException {
        return run.update(getConnection(), "DELETE FROM " + TABLE_NAME
                + " WHERE LOGIN=?", login);
    }

    public int update() throws SQLException {
        return run.update(getConnection(), "UPDATE " + TABLE_NAME
                + " SET EMAIL = ?," + " SALT = ?," + " HASHED_PASSWORD = ?,"
                + " ADMIN = ?," + " TOKEN = ?" + " WHERE LOGIN=?", email, salt,
                hashedPassword, admin, token, login);
    }

    /**
     * Create a set of initial users.
     * 
     * @throws SQLException if creation fails
     */
    public static void init() throws SQLException {
        User admin = new User(Constants.ADMIN_LOGIN, "", "");
        admin.setAdmin(1);
        admin.createToken();
        admin.create();
        User shared = new User(Constants.SHARED_LOGIN, "", "");
        shared.create();
    }

    public String getLogin() {
        return login;
    }

    public void setLogin(String login) {
        this.login = login;
    }

    public void setPassword(String password) {
        if (salt == null) {
            salt = getRandomString(10);
        }
        hashedPassword = encrypt(password, salt);
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getSalt() {
        return salt;
    }

    public void setSalt(String salt) {
        this.salt = salt;
    }

    public String getHashed_password() {
        return hashedPassword;
    }

    public void setHashed_password(String hashedPassword) {
        this.hashedPassword = hashedPassword;
    }

    public int getAdmin() {
        return admin;
    }

    public void setAdmin(int admin) {
        this.admin = admin;
    }

    public boolean isAdmin() {
        return admin == 1;
    }

    public static String getRandomString(int len) {
        Random generator = new Random();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < len; i++) {
            sb.append(CHARS.charAt(generator.nextInt(CHARS.length())));
        }
        return sb.toString();
    }

    public static String encrypt(String phrase, String salt) {
        return DigestUtils.shaHex(phrase + salt);
    }

    public User handle(ResultSet rs) throws SQLException {
        if (!rs.next()) {
            return null;
        }
        User user = new User();
        ResultSetMetaData meta = rs.getMetaData();
        int cols = meta.getColumnCount();

        for (int i = 0; i < cols; i++) {
            switch (i) {
            case 0:
                user.setLogin(rs.getString(i + 1));
                break;
            case 1:
                user.setEmail(rs.getString(i + 1));
                break;
            case 2:
                user.setSalt(rs.getString(i + 1));
                break;
            case 3:
                user.setHashed_password(rs.getString(i + 1));
                break;
            case 4:
                user.setAdmin(rs.getInt(i + 1));
                break;
            case 5:
                user.setToken(rs.getString(i + 1));
                break;
            }
        }

        return user;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((email == null) ? 0 : email.hashCode());
        result = prime * result
                + ((hashedPassword == null) ? 0 : hashedPassword.hashCode());
        result = prime * result + ((login == null) ? 0 : login.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        User other = (User) obj;
        if (email == null) {
            if (other.email != null) {
                return false;
            }
        } else if (!email.equals(other.email)) {
            return false;
        }
        if (hashedPassword == null) {
            if (other.hashedPassword != null) {
                return false;
            }
        } else if (!hashedPassword.equals(other.hashedPassword)) {
            return false;
        }
        if (login == null) {
            if (other.login != null) {
                return false;
            }
        } else if (!login.equals(other.login)) {
            return false;
        }
        return true;
    }

    public String getToken() {
        return token;
    }

    public void setToken(String token) {
        this.token = token;
    }

    @Override
    public String toString() {
        return "User [login=" + login + ", email=" + email + ", salt=" + salt
                + ", hashedPassword=" + hashedPassword + ", admin=" + admin
                + ", token=" + token + "]";
    }

}
